﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using DCT.CodeBox.ConfigParser.Utilities;

namespace DCT.CodeBox.ConfigParser.Codes
{
    public class XmlConfigParser : IConfigParser
    {
        private static string DEFAULT_ROOT_TAG = "root";
        private static string DEFAULT_VAL_TAG = "val";
        private static string DEFAULT_OBJECT_TAG = "object";
        private static string DEFAULT_LIST_TAG = "list";
        private static string DEFAULT_KEY_ATTR = "key";

        private string rootTag = DEFAULT_ROOT_TAG;
        private string valTag = DEFAULT_VAL_TAG;
        private string objectTag = DEFAULT_OBJECT_TAG;
        private string listTag = DEFAULT_LIST_TAG;
        private string keyAttr = DEFAULT_KEY_ATTR;

        private XmlNode rootNode = null;
        private KeyValueCache<object> cache = new KeyValueCache<object>();

        public string RootTag { get { return this.rootTag; } set { this.rootTag = value; } }
        public string ValTag { get { return this.valTag; } set { this.valTag = value; } }
        public string ObjectTag { get { return this.objectTag; } set { this.objectTag = value; } }
        public string ListTag { get { return this.listTag; } set { this.listTag = value; } }
        public string KeyAttr { get { return this.keyAttr; } set { this.keyAttr = value; } }
        
        public XmlNode RootNode { get { return this.rootNode; } }

        public XmlConfigParser()
        {
            this.rootNode = XmlUtility.CreateEmptyXmlDocument(this.RootTag).DocumentElement; 
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="config"></param>
        /// <returns></returns>
        public IConfigParser Load(object config)
        {
            if (config == null || !(config is string)) {
                throw new ArgumentException("config不能为空，且必须为字符串");
            }
            this.cache.Reset();
            this.rootNode = XmlUtility.Load(config.ToString()).DocumentElement;
            return this;
        }

        public IConfigParser Clear()
        {
            this.cache.Reset();
            this.rootNode.RemoveAll();
            return this;
        }

        public string ParseToString()
        {
            return XmlUtility.DEFAULT_XML_DECLARATION + this.rootNode.OuterXml;
        }

        public string GetVal(string key, string defaultVal)
        {
            //val[@key='xx']
            string xpath = string.Format("{0}[@{1}='{2}']"
                , this.ValTag, this.KeyAttr, key);
            return cache.GetValue(xpath, xpathKey =>
            {
                return XmlUtility.GetNodeInnerText(this.rootNode.SelectSingleNode(xpathKey));
            }) as string;
        }

        public IDictionary<string, string> GetKV(string key)
        {
            Dictionary<string, string> result = new Dictionary<string, string>();
            //object[@key='xx']/val
            string xpath = string.Format("{0}[@{1}='{2}']/{3}"
                , this.ObjectTag, this.KeyAttr, key , this.ValTag);

            XmlNodeList xmlNodeList = this.cache.GetValue(xpath, xpathKey => 
            {
                return this.rootNode.SelectNodes(xpathKey);
            }) as XmlNodeList;

            foreach (XmlNode node in xmlNodeList)
            {
                string kvKey = XmlUtility.GetNodeAttr(node, this.KeyAttr);
                if (!string.IsNullOrEmpty(kvKey))
                {
                    result.Add(kvKey, XmlUtility.GetNodeInnerText(node));
                }
            }
            return result;
        }

        public IEnumerable<IDictionary<string, string>> GetListKV(string key)
        {
            //list[@key='xx']/object
            string xpath = string.Format("{0}[@{1}='{2}']/{3}"
                , this.ListTag, this.KeyAttr, key, this.ObjectTag);

            XmlNodeList xmlNodeList = this.cache.GetValue(xpath, xpathKey =>
            {
                return this.rootNode.SelectNodes(xpathKey);
            }) as XmlNodeList;

            foreach (XmlNode node in xmlNodeList)
            {
                Dictionary<string, string> kv = new Dictionary<string, string>();

                //node为一个object节点
                XmlNodeList objectNodeList = node.SelectNodes(this.ValTag);
                foreach (XmlNode propertyNode in objectNodeList)
                {
                    string kvKey = XmlUtility.GetNodeAttr(propertyNode, this.KeyAttr);
                    if (!string.IsNullOrEmpty(kvKey))
                    {
                        kv[kvKey] = XmlUtility.GetNodeInnerText(propertyNode);
                    }
                }

                yield return kv;
            }
        }

        public IEnumerable<string> GetListVal(string key)
        {
            //list[@key='xx']/val
            string xpath = string.Format("{0}[@{1}='{2}']/{3}"
                , this.ListTag, this.KeyAttr, key, this.ValTag);

            XmlNodeList xmlNodeList = this.cache.GetValue(xpath, xpathKey =>
            {
                return this.rootNode.SelectNodes(xpathKey);
            }) as XmlNodeList;

            foreach (XmlNode node in xmlNodeList)
            {
                yield return XmlUtility.GetNodeInnerText(node);
            }
        }

        public void SetVal(string key, string val)
        {
            XmlNode valNode = this.CreateOrGetNode(true, this.ValTag, key);
            valNode.InnerText = val;

            this.rootNode.AppendChild(valNode);
            this.cache.Reset();
        }

        public void SetKV(string key, IDictionary<string, string> kv)
        {
            XmlNode objectNode = this.CreateOrGetNode(true, this.ObjectTag, key);
            foreach (KeyValuePair<string, string> item in kv)
            {
                XmlNode valNode = this.CreateOrGetNode(false, this.ValTag, item.Key);
                valNode.InnerText = item.Value;
                objectNode.AppendChild(valNode);
            }

            this.rootNode.AppendChild(objectNode);
            this.cache.Reset();
        }

        public void SetListKV(string key, IEnumerable<IDictionary<string, string>> kvList)
        {
            XmlNode listNode = this.CreateOrGetNode(true, this.ListTag, key);
            foreach (IDictionary<string, string> kv in kvList)
            {
                XmlNode objectNode = this.CreateNode(this.ObjectTag);
                foreach (KeyValuePair<string, string> item in kv)
                {
                    XmlNode valNode = this.CreateOrGetNode(false, this.ValTag, item.Key);
                    valNode.InnerText = item.Value;
                    objectNode.AppendChild(valNode);
                }
                listNode.AppendChild(objectNode);
            }

            this.rootNode.AppendChild(listNode);
            this.cache.Reset();
        }

        public void SetListVal(string key, IEnumerable<string> listVal)
        {
            XmlNode listNode = this.CreateOrGetNode(true, this.ListTag, key);
            foreach (string item in listVal)
            {
                XmlNode valNode = this.CreateNode(this.ValTag);
                valNode.InnerText = item;
                listNode.AppendChild(valNode);
            }

            this.rootNode.AppendChild(listNode);
            this.cache.Reset();
        }

        private XmlNode CreateNode(string tag)
        {
            return this.rootNode.OwnerDocument.CreateElement(tag);
        }

        private XmlNode CreateOrGetNode(bool findOriginalNode ,string tag, string key)
        {            
            XmlNode node = null;
            if (findOriginalNode)
            {
                string xpath = string.Format("{0}[@key='{1}']", tag, key);
                node = this.rootNode.SelectSingleNode(xpath);
            }
            if (node != null)
            {
                node.RemoveAll();
            }
            else
            {
                node = this.rootNode.OwnerDocument.CreateElement(tag);
            }
            XmlAttribute xmlAttr = this.rootNode.OwnerDocument.CreateAttribute(this.KeyAttr);
            xmlAttr.Value = key;
            node.Attributes.Append(xmlAttr);
            return node;
        }

        #region 工具转换
        public static XmlConfigParser Parse(IDictionary<string,string> dict)
        {
            XmlConfigParser xcp = new XmlConfigParser();

            foreach (var item in dict)
            {
                xcp.SetVal(item.Key, item.Value);
            }

            return xcp;
        }
        #endregion
    }
}
